<?php
/*
 * Created on 21.12.2005
 *
 */

class PSPLexer {

    var $code;
    var $tokenlist;
    var $inTag = false;
    var $inProofTag = false;
    var $currentTagBegin;
    var $line = 1;
    var $strlen = 0;
    // start
    var $s = 0;
    // counter
    var $i = 0;
    var $endLastTag = 0;
    // pointer for nextToken
    var $tokenpointer = -1;
    var $namespace;
    // for converted opening tags, stores hierarchy 
    var $tagstack;
    // counts for each tag on stack, 
    var $tagcstack;
    // should lexing be stopped (because of an error, ...)
	var $stopLex = false;

    function PSPLexer ($namespace, $code) 
    {
        $this->namespace = $namespace;
        $this->code = $code;
    }
    
    /**
     * static
     * 
     * @param string $fname Name of the file to load an return
     * @return string The content of the loaded file
     */
    function loadFile ($fname)
    {
        return join("", file($fname));
    }
    
    /**
     * Stop the lexing process
     *
     */
    function stopLex() {
        $this->stopLex = true;
    }
    
    /**
     * Does the file have a next token?
     */
    function hasNextToken()
    {
        return (isset($this->tokenlist[$this->tokenpointer+1]));
    }
    
    /**
     * Move to the next token
     *
     */
    function nextToken()
    {
        $this->tokenpointer++;
    }
    
    /**
     * Get the next token
     */
    function getNextToken()
    {
        return $this->tokenlist[$this->tokenpointer];
    }
    
    /**
     * Move back to the first token
     */
    function rewind ()
    {
        $this->tokenpointer = 0;
    }
    
    /**
     * Check whether the tag at the given position is in the given namespace
     * 
     * @param string $ns Namespace to test against
     * @param int    $i  Character position in the lexed file
     */
    function checkTagNamespace ($ns, $i)
    {
        $strlen = strlen($ns);
        for ($c = 0; $c < $strlen; $c ++)
        {
            if ($ns[$c] != $this->code[$i + $c])
                return false; 
        }
        if ($this->code[$i + $strlen] != ':')
            return false;
        return true;
    }
    
    /**
     * Check for the tag at the given position whether it is in one of the configured namespaces
     * 
     * @param int    $i  Character position in the lexed file
     */
    function checkTagNamespaces ($i)
    {
        if (is_array($this->namespace))
        {
            foreach ($this->namespace as $ns)
            {
                if ($this->checkTagNamespace($ns, $i))
                {
                    $this->lastNS = $ns;
                    return true;
                }
            }
            return false;
        }
        else
        {
            $this->lastNS = $this->namespace;
            return $this->checkTagNamespace($this->namespace, $i);
        }
    }

    /**
     * Do the lexing
     */
    function lex () 
    {
        $this->strlen = strlen($this->code);
        for ($this->i = 0; $this->i < $this->strlen; $this->i++) 
        {
//            if ($eaten) 
//            {
//                print $this->code[$this->i-4].$this->code[$this->i-3].$this->code[$this->i-2].$this->code[$this->i-1].$this->code[$this->i].$this->code[$this->i+1].$this->code[$this->i+2].$this->code[$this->i+3].$this->code[$this->i+4] . "\n";
//                $eaten = false;
//            }
            if (($this->code[$this->i] == '<' &&
                $this->checkTagNamespaces($this->i + 1)) || 
                ($this->code[$this->i] == '<' && 
                $this->code[$this->i + 1] == '/' && 
                $this->checkTagNamespaces($this->i + 2)))
            {
                
                $token['type']  = 'T_CDATA';
                $token['value'] = substr($this->code, $this->s, $this->i-$this->s);
                $token['line'] = $this->line;
                $this->tokenlist[] = $token;
                $this->s = $this->i; 
                $this->eatTag();
                $eaten = true;
                $this->i--;
            }
            elseif ($this->inProofTag 
                && $this->code[$this->i] == "p" 
                && $this->code[$this->i+1] == "s"
                && $this->code[$this->i+2] == "p"
                && $this->code[$this->i+3] == "c"
                && $this->code[$this->i+4] == "="
                && $this->code[$this->i+5] == '"'
            )
            {
                for ($i = $this->i + 6; $i < $this->strlen && $this->code[$i] != '"'; $i++){
                }
                $transTag = substr($this->code, $this->i+6, ($i-($this->i+6)));
                $parts = split(':', $transTag);
                $this->lastNS = $parts[0];
                $this->i = $this->currentTagBegin+1;
                $token['type']  = 'T_CDATA';
                $token['value'] = substr($this->code, $this->s, $this->i-$this->s-1);
                $token['line'] = $this->line;
                $this->tokenlist[] = $token;
                $this->s = $this->i; 
                $this->eatConvertTag($parts[1]);
                $eaten = true;
                $this->i--;
                $this->inProofTag = false;
            }
            elseif ($this->code[$this->i] == "<" 
                && $this->code[$this->i+1] == "?"
                && $this->code[$this->i+2] == "p"
                && $this->code[$this->i+3] == "h"
                && $this->code[$this->i+4] == "p"
            )
            {
                $token['type']  = 'T_CDATA';
                $token['value'] = substr($this->code, $this->s, $this->i-$this->s);
                $token['line'] = $this->line;
                $this->tokenlist[] = $token;
                $this->s = $this->i; 
                for (;!($this->code[$this->i] == '?' && $this->code[$this->i+1] == '>') && $this->i < $this->strlen; $this->i++)
                {
                    if ($this->code[$this->i] == "\n")
                        $this->line++;
                }
                $token['type']  = 'T_SCRIPTLET';
                $token['value'] = substr($this->code, $this->s+5, $this->i-$this->s-5);
                $token['line'] = $this->line;
                $this->tokenlist[] = $token;
                $this->s = $this->i+2;
                $this->endLastTag = $this->s;
            }
            elseif ($this->code[$this->i] == "<")
            {
                $this->inProofTag = true;
                $this->currentTagBegin = $this->i;
                if ($c = count($this->tagstack))
                {
                    $top = $this->top();
                    if ($this->findKeyword($this->i+1, $top[0]))
                    {
                        $this->tagcstack[$c-1]++;
                    }
                    elseif ($this->code[$this->i+1] == "/" && $this->findKeyword($this->i+2, $top[0]))
                    {
                        if ($this->tagcstack[$c-1] < 1)
                        {
                            $token['type']  = 'T_CDATA';
                            $token['value'] = substr($this->code, $this->s, $this->i-$this->s);
                            $token['line'] = $this->line;
                            $this->tokenlist[] = $token;
                            $token['type']  = 'T_BEGIN_CLOSE_TAG';
                            unset($token['value']);
                            $token['ns']    = $top[1];
                            $token['line']  = $this->line;
                            $this->tokenlist[] = $token;
                            $token['type'] = 'T_IDENTIFIER';
                            $token['value'] = $top[2];
                            unset($token['ns']);
                            $token['line'] = $this->line;
                            $this->tokenlist[] = $token;
                            $token['type'] = 'T_END_TAG';
                            $token['value'] = '';
                            $token['line'] = $this->line;
                            $this->tokenlist[] = $token;
                            array_pop($this->tagstack);
                            unset($this->tagcstack[$c-1]);
                            while ($this->i < $this->strlen && $this->code[$this->i] != ">")
                            {
                                $this->i++;
                            }
                            $this->s = $this->i + 1;
                            $this->endLastTag = $this->s;
                        }
                        $this->tagcstack[$c-1]--;
                    }
                }
            }
            elseif ($this->code[$this->i] == "\$" 
                && $this->code[$this->i+1] == "{"
            )
            {
                $token['type']  = 'T_CDATA';
                $token['value'] = substr($this->code, $this->s, $this->i-$this->s);
                $token['line'] = $this->line;
                $this->tokenlist[] = $token;
                $this->s = $this->i; 
                for (;!($this->code[$this->i] == '}') && $this->i < $this->strlen; $this->i++)
                {
                    if ($this->code[$this->i] == "\n")
                        $this->line++;
                }
                $token['type']  = 'T_EL_CODE';
                $token['value'] = substr($this->code, $this->s, $this->i-$this->s+1);
                $token['line'] = $this->line;
                $this->tokenlist[] = $token;
                $this->s = $this->i+1;
                $this->endLastTag = $this->s;
            }
            if ($this->stopLex)
                return;
            elseif ($this->code[$this->i] == "\n")
            {
                $this->line++;
            } 
        }
        $token['type'] = 'T_CDATA';
        $token['value'] = substr($this->code, $this->endLastTag, $this->strlen-$this->endLastTag);
        $token['line'] = $this->line;
        $this->tokenlist[] = $token;
    }
    
    function top ()
    {
        return $this->tagstack[count($this->tagstack)-1];
    }
    
    function findKeyword($i, $str)
    {
        $strlen = strlen($str);
        for ($c = 0; $c < $strlen; $c ++)
        {
            if ($str[$c] != $this->code[$i + $c])
                return false; 
        }
        if (preg_match('/\w/', $this->code[$i + $strlen]))
            return false;
        return true;
    }

    function eatConvertTag ($newtagname)
    {
        $token = array();
        if ($this->code[$this->s + 1] == '/')
        {
            $token['type'] = 'T_BEGIN_CLOSE_TAG';
            $this->inTag = true;
            $this->s++;
        }
        else
        {
            $token['type'] = 'T_BEGIN_OPEN_TAG';
            $this->inTag = true;
        }
        $token['ns'] = $this->lastNS;
        $token['line'] = $this->line;
        $this->tokenlist[] = $token;
        $this->i = $this->s;
        while (preg_match("/\w/", $this->code[$this->i]))
        {
            $this->i++;
        }
        if ($this->tagcstack[count($this->tagstack)-1] > 0 && $this->tagstack[count($this->tagstack)-1][0] == $newtagname)
        {
            $this->tagcstack[count($this->tagstack)-1]--;
        }
        $this->tagstack[] = array(substr($this->code, $this->s, $this->i-$this->s), $token['ns'], $newtagname);
        $this->tagcstack[count($this->tagstack)-1] = 0;
        $this->s = $this->i;
        if (!$newtagname) {
            print "<b>PSPLexer:</b> Tag name missing in line " . $this->line . "\n";
            return $this->stopLex();
        }
        $token['type'] = 'T_IDENTIFIER';
        $token['value'] = $newtagname;
        $token['line'] = $this->line;
        $this->tokenlist[] = $token;
        $this->eatWhitespaces();
        while ($this->i < $this->strlen)
        {
            if ($this->endTag(true))
                break;
            $this->eatWhitespaces();
            $this->attribute();
            $this->eatWhitespaces();
        }
    }
    
    function eatTag () 
    {
        $token = array();
        if ($this->code[$this->s + 1] == '/')
        {
            $token['type'] = 'T_BEGIN_CLOSE_TAG';
            $this->inTag = true;
            $this->s += (3 + strlen($this->lastNS));
            $this->i = $this->s;
        }
        else
        {
            $token['type'] = 'T_BEGIN_OPEN_TAG';
            $this->inTag = true;
            $this->s += (2 + strlen($this->lastNS));
            $this->i = $this->s;
        }
        $token['ns'] = $this->lastNS;
        $token['line'] = $this->line;
        $this->tokenlist[] = $token;
        if (!$this->identifier()) {
            print "<b>PSPLexer:</b> Tag name missing in line " . $this->line . "\n";
            return $this->stopLex();
        }
        $this->eatWhitespaces();
        while ($this->i < $this->strlen)
        {
            if ($this->endTag())
                break;
            $this->eatWhitespaces();
            $this->attribute();
            $this->eatWhitespaces();
        }
    }
    
    function endTag ($isConverted = false)
    {
        if ($this->code[$this->s] == '>' || ($this->code[$this->s] == '/' && $this->code[$this->s+1] == '>'))
        {
            if ($this->code[$this->s] == '>')
            {
                $token['type'] = 'T_END_TAG';
                $this->inTag = false;
                $token['line'] = $this->line;
                $this->s ++;
            }
            else
            {
                if ($isConverted)
                {
                    unset($this->tagcstack[count($this->tagstack)-1]);
                    array_pop($this->tagstack);
                }
                $token['type'] = 'T_END_FIN_TAG';
                $this->inTag = false;
                $token['line'] = $this->line;
                $this->s += 2;
            }
            $this->i = $this->s;
            $this->tokenlist[] = $token;
            $this->endLastTag = $this->i;
            return true;
        }
    }
    
    function attribute ()
    {
        if (!$this->identifier())
        {
            print "Error in middle of: ".htmlentities(substr($this->code, $this->i-10, 20));
            print "<b>PSPLexer:</b> Attribute name or tag end expected in line " . $this->line . "\n";
            return $this->stopLex();
        }
        $this->eatWhitespaces();
        if ($this->code[$this->s] == '=')
        {
            $token['type'] = 'T_ASSIGN';
            $token['line'] = $this->line;
            $this->tokenlist[] = $token;
            $this->s ++;
            $this->i = $this->s;
        }
        else
        {
            print "<b>PSPLexer:</b> Assignment expected in line " . $this->line . "\n";
            return $this->stopLex();
        }
        $this->eatWhitespaces();
        if (!$this->attributeValue())
        {
            print "<b>PSPLexer:</b> Attribute value expected in line " . $this->line . "\n";
            return $this->stopLex();            
        }
    }
    
    function attributeValue ()
    {
        if ($this->code[$this->s] != '"') {
            print "<b>PSPLexer:</b> Double quote expected in " . $this->line . "\n";
            return $this->stopLex();
        }
        $this->s ++;
        $this->i = $this->s;
        while ($this->code[$this->i] != '"' && $this->i < $this->strlen)
        {
            if ($this->code[$this->i] == "\n")
                $this->line++;
            $this->i++;
        }
        $token['type'] = 'T_VALUE';
        $token['value'] = substr($this->code, $this->s, $this->i - $this->s);
        $token['line'] = $this->line;
        $this->tokenlist[] = $token;
        $this->i ++;
        $this->s = $this->i;
        return true;
    }
    
    function eatWhitespaces ()
    {
        while ($this->code[$this->i] == "\n" || $this->code[$this->i] == "\r" 
            || $this->code[$this->i] == "\t" || $this->code[$this->i] == ' ')
        {
            if ($this->code[$this->i] == "\n")
                $this->line++;
            $this->i++;
        }
        $this->s = $this->i;
    }
    
    function identifier ()
    {
        if (!$this->inTag)
            return;
        $token = array();
        while (preg_match("/\w/", $this->code[$this->i]))
        {
            $this->i++;
        }
        if ($this->i-$this->s)
        {
            $token['type'] = 'T_IDENTIFIER';
            $token['value'] = substr($this->code, $this->s, $this->i-$this->s);
            $token['line'] = $this->line;
            $this->tokenlist[] = $token;
            $this->s = $this->i;
            return true;
        }
        return false;
    }
    
}

?>
